/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package jesstalk.chatsystem;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import jesstalk.Db;
import jesstalk.chatsystem.chatrequest.*;
import jesstalk.chatsystem.chatresponse.*;

/**
 *
 * @author Ecky
 */
public class ChatController {

    //Constants
    private static final long OL_TIMEOUT = 30 * 1000; //30 secs

    //Vars
    private Db  _db;
    private Map<String, RoomInfo>   _rooms;
    private Map<String, UserInfo>   _users;

    public ChatController(Db db) {
        _db     = db;
        _rooms  = new ConcurrentHashMap<String, RoomInfo>();
        _users  = new ConcurrentHashMap<String, UserInfo>();
    }
    
    public Object processInfo(String request) {
        
        InfoResponse retval = new InfoResponse();
        retval.room = _rooms.get(request);
        retval.user = _users.get(request);
        return retval;
    }

    public void delUserFromSystem(String username) {
        //Remove user from list
        _users.remove(username);

        //Remove the user from any room
        Set<Entry<String, RoomInfo>> ee = _rooms.entrySet();
        for(Entry<String, RoomInfo> e : ee) {
            RoomInfo r = e.getValue();
            if(r.userJoins.containsKey(username)) {
                r.userJoins.remove(username);
                r.chats.add(0, MessageInfo.createSystemMsg(username + " has quit. Reason : Deleted by admin"));
            }
        }
    }

    public void setUserAsMod(String username, int categId) {
        try {
            //Get user
            UserInfo user = _users.get(username);
            if (user == null) {
                return;
            }

            //Add moderating info to that user
            user.moderatingId.add(categId);
            ResultSet rs = _db.doQuery("SELECT nama FROM kategori WHERE id = '" + categId + "'");
            user.moderating.add(rs.getString(1));

            //Set last update to now
            user.lastUpdate = System.currentTimeMillis();
        } catch (SQLException ex) {
            Logger.getLogger(ChatController.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void banUserFromSystem(String username) {
        //Quit user
        UserInfo user = _users.get(username);
        if(user == null) return;
        user.isBanned = true;
    }

    public void delCategoryFromSystem() {
        //Check each room that has invalid room category
        List<RoomInfo> arr = new ArrayList<RoomInfo>();
        Set<Entry<String, RoomInfo>> ee = _rooms.entrySet();
        for(Entry<String, RoomInfo> e : ee) {
            try {
                //Is invalid ?
                RoomInfo r = e.getValue();
                ResultSet rs = _db.doQuery("SELECT * FROM kategori WHERE id = '" + r.categoryId + "'");
                if (!rs.next()) {
                    arr.add(r);
                }
            } catch (SQLException ex) {
                Logger.getLogger(ChatController.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        for(RoomInfo r : arr) {
            //Remove from system
            _rooms.remove(r.name);

            //Kick all user inside that room
            Set<Entry<String, Long>> uu = r.userJoins.entrySet();
            for(Entry<String, Long> u : uu) {
                _users.get(u.getKey()).kickedFrom.add(r.name);
                _users.get(u.getKey()).pvChats.add(MessageInfo.createSystemMsg(r.name + ": Room deleted"));
            }

            //Remove ownership from that room
            _users.get(r.owner).room.remove(r.name);
        }
    }

    public Object processChatRequest(ChatRequest request) throws SQLException {
        //Prepare variables
        ChatResponse    response    = new ChatResponse();

        //Get user info
        UserInfo client = _users.get(request.username);
        if(client == null) {
            client = createUserInfo(request.username);
            if(client == null) {
                //send error - user not found
                response.addError(ErrorInfo.clientNotFound());
                return response;
            }
            _users.put(request.username, client);
        }

        //Am i banned?
        if(client.isBanned) {
            //Error - user banned
            response.addError(ErrorInfo.clientIsBanned());
            return response;
        }

        
        /// PROCESS REQUEST

        //Add friend request
        if(request.addFriend != null) {
            for(String name : request.addFriend) {
                //Get target Id
                UserInfo target = _users.get(name);

                //If not found, send error
                if(target == null) {
                    //Write error, username not found
                    response.addError(ErrorInfo.userNotFound(name));
                    continue;
                }

                //Is user too idiot to addFriend himself?
                if(target.id == client.id) {
                    //No need to write error, user is idiot
                    continue;
                }

                //Check whether it already befriend
                ResultSet r = _db.doQuery("SELECT * FROM friend WHERE idFollower = '" + client.id + "' AND idFollowing = '" + target.id + "'");
                if(r.next()) {
                    //No need to Write error, already friend
                    continue;
                }

                //Insert to DB
                _db.doQueryAffectDB("INSERT INTO friend (id, idFollower, idFollowing) VALUES (NULL, '" + client.id + "', '" + target.id +"')");
            }
        }

        if(request.createRoom != null) {
            for(CreateRoomRequest r : request.createRoom) {
                //Check whether the roomName is available
                if(_rooms.containsKey(r.roomName)) {
                    //Send error - roomName alredy exists
                    response.addError(ErrorInfo.roomNameUnavailable(r.roomName));
                    continue;
                }

                //Check whether client allowed to add another room
                if(client.room.size() >= client.nRoom) {
                    //Send error - client has too much room
                    response.addError(ErrorInfo.clientHasTooManyRooms());
                    continue;
                }

                //Check whether the category is valid
                String query = "(SELECT id FROM kategori WHERE id = '0')";
                for(int i = 0; i < r.categories.size(); ++i) {
                    query = "(SELECT id FROM kategori WHERE nama = '" + r.categories.get(i) + "' AND idParent IN " + query + ")";
                }
                ResultSet rs = _db.doQuery(query);
                if(!rs.next()) {
                    //Send error - category not found
                    response.addError(ErrorInfo.categoryNotFound(r.categories));
                    continue;
                }

                //Add that room
                RoomInfo room = new RoomInfo();
                _rooms.put(r.roomName, room);
                System.out.println(_rooms);
                room.bans = new ArrayList<String>();
                room.name = r.roomName;
                room.categoryId = rs.getInt(1);
                room.chats = new LinkedList<MessageInfo>();
                room.owner = client.username;
                room.persist = false;
                room.bots = new ArrayList<BotInfo>();
                room.categs = r.categories;
                room.userJoins = new ConcurrentHashMap<String, Long>();

                //Add client to that room
                room.userJoins.put(client.username, System.currentTimeMillis());
                if(response.updatedOpenedRooms == null) response.updatedOpenedRooms = new ArrayList<RoomInfo>();
                response.updatedOpenedRooms.add(roomInfoForClient(client, r.roomName, client.lastConn, true));

                //Write system msg to that room
                room.chats.add(0, MessageInfo.createSystemMsg("Room created"));
                room.chats.add(0, MessageInfo.createSystemMsg(client.username + " joined the room"));

                //Add room to user list
                client.room.add(room.name);
            }
        }

        if(request.exitRoom != null) {
            for(String s : request.exitRoom) {
                //Get room
                RoomInfo r = _rooms.get(s);

                //Check whether the roomName existed
                if(r == null) {
                    //Send error - roomName alredy exists
                    response.addError(ErrorInfo.roomNameUnavailable(s));
                    continue;
                }

                //Quit the room
                quitRoom(client, r);

                //Write reponse
                if(response.closedOpenedRooms == null) response.closedOpenedRooms = new ArrayList<String>();
                response.closedOpenedRooms.add(s);
            }
        }

        if(request.joinRoom != null) {
            for(String s : request.joinRoom) {
                //Get room
                RoomInfo r = _rooms.get(s);
                
                //Check whether the roomName existed
                if(r == null) {
                    //Send error - roomName not exists
                    response.addError(ErrorInfo.roomIsNotExisted(s));
                    continue;
                }

                //Check whether I'm banned
                if(r.bans.indexOf(client.username) >= 0) {
                    //Send error - You are banned
                    response.addError(ErrorInfo.clientIsBannedFromRoom(s));
                    continue;
                }

                //If user isn't in the room, update the room
                if(!r.userJoins.containsKey(client.username)) {
                    r.userJoins.put(client.username, System.currentTimeMillis());
                    r.chats.add(0, MessageInfo.createSystemMsg(client.username + " joined the room"));
                }

                //Write response
                if(response.updatedOpenedRooms == null) response.updatedOpenedRooms = new ArrayList<RoomInfo>();
                response.updatedOpenedRooms.add(roomInfoForClient(client, s, client.lastConn, true));
            }
        }

        if(request.chatToRoom != null) {
            for(TargetMsgParams tm : request.chatToRoom) {
                
                //Get and validate target
                RoomInfo r = _rooms.get(tm.target);
                if(r == null) {
                    //write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(tm.target));
                    continue;
                }

                //Create messageInfo
                MessageInfo msg = MessageInfo.createChatMsg(client.username, null, tm.msg);

                //add it to room
                r.chats.add(0, msg);

                //Let bot response
                for(BotInfo b : r.bots) {
                    MessageInfo mmsg = b.process(msg);
                    if(mmsg != null) r.chats.add(0, mmsg);
                }
            }
        }

        if(request.chatToUser != null) {
            for(TargetMsgParams tm : request.chatToUser) {
                //Get and validate target
                UserInfo target = _users.get(tm.target);
                if(target == null) {
                    //Error - user not exists
                    response.addError(ErrorInfo.userNotFound(tm.target));
                    continue;
                }

                //Create messageInfo and add it to that user
                target.pvChats.add(MessageInfo.createChatMsg(client.username, client.username, tm.msg));

                //Also, add message to my pvChat
                client.pvChats.add(MessageInfo.createChatMsg(client.username, tm.target, tm.msg));
            }
        }

        if(request.kickUser != null) {
            for(RoomUserParams ru : request.kickUser) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(ru.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(ru.roomName));
                    continue;
                }

                //Check whether the user existed in that room
                if(!r.userJoins.containsKey(ru.userName)) {
                    //Write error - user is not in the room
                    response.addError(ErrorInfo.userIsNotInRoom(ru.userName, ru.roomName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //Rem the user from list
                r.userJoins.remove(ru.userName);

                //Inform target user that he is kicked
                UserInfo target = _users.get(ru.userName);
                target.kickedFrom.add(ru.roomName);
                target.pvChats.add(MessageInfo.createSystemMsg(ru.roomName + " : " + ru.userName + " is kicked by " + client.username));

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(ru.userName + " is kicked by " + client.username));
            }
        }

        if(request.banUser != null) {
            for(RoomUserParams ru : request.banUser) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(ru.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(ru.roomName));
                    continue;
                }

                //Check whether the user existed
                UserInfo user = _users.get(ru.userName);
                if(user == null) {
                    //Write error - user not found
                    response.addError(ErrorInfo.userNotFound(ru.userName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //ban the user from room
                r.bans.add(ru.userName);

                //kick user
                if(r.userJoins.containsKey(ru.userName)) {
                    r.userJoins.remove(ru.userName);

                    //Inform target user that he is kicked
                    UserInfo target = _users.get(ru.userName);
                    target.kickedFrom.add(ru.roomName);
                    target.pvChats.add(MessageInfo.createSystemMsg(ru.roomName + " : " + ru.userName + " is banned by " + client.username));
                }

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(ru.userName + " is banned by " + client.username));
            }
        }

        if(request.unbanUser != null) {
            for(RoomUserParams ru : request.unbanUser) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(ru.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(ru.roomName));
                    continue;
                }

                //Check whether the user existed
                int idx = r.bans.indexOf(ru.userName );
                if(idx < 0) {
                    //Write error - user not found
                    response.addError(ErrorInfo.userNotFound(ru.userName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //Rem the user from list
                r.bans.remove(idx);

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(ru.userName + " is unbanned by " + client.username));
            }
        }

        if(request.setRoomOwner != null) {
            for(RoomUserParams ru : request.setRoomOwner) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(ru.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(ru.roomName));
                    continue;
                }

                //Check whether the user existed in that room
                if(!r.userJoins.containsKey(ru.userName)) {
                    //Write error - user not found
                    response.addError(ErrorInfo.userNotFound(ru.userName));
                    continue;
                }
                
                //Get target
                UserInfo target = _users.get(ru.userName);

                //Check whether target can be set as owner
                if(target.room.size() >= target.nRoom) {
                    //Write error - user cannot be set as owner
                    response.addError(ErrorInfo.userNotFound(ru.userName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //Change ownership
                r.owner = ru.userName;
                client.room.remove(ru.roomName);
                target.room.add(ru.roomName);

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(ru.userName + " is set as room owner by " + client.username));
            }
        }

        if(request.setRoomPersistance != null) {
            for(RoomPersistParams rp : request.setRoomPersistance) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(rp.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(rp.roomName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //Set persist
                r.persist = rp.persist;

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(client.username + " set room persistance to " + (r.persist ? "true" : "false")));
            }
        }

        if(request.addBot != null) {
            for(BotRequest br : request.addBot) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(br.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(br.roomName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //Add the bot
                r.bots.add(new BotInfo(br.botName, br.botType));

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(client.username + " added bot " + br.botName));
            }
        }

        if(request.remBot != null) {
            for(RoomUserParams ru : request.remBot) {
                //Check whether the roomName existed
                RoomInfo r = _rooms.get(ru.roomName);
                if(r == null) {
                    //Write error - room not existed
                    response.addError(ErrorInfo.roomIsNotExisted(ru.roomName));
                    continue;
                }

                //Check whether I'm the owner of the room and mod
                boolean isOwner = r.owner.equals(client.username) || client.moderatingId.indexOf(r.categoryId) >= 0;
                if(!isOwner) {
                    //Write error - client doesn;t have previledge to do that
                    response.addError(ErrorInfo.clientHasNoPreviledge(client.username));
                    continue;
                }

                //remove the bot
                int idx = r.bots.indexOf(ru.userName);
                if(idx < 0) {
                    response.addError(ErrorInfo.userIsNotInRoom(ru.userName, ru.roomName));
                    continue;
                }
                r.bots.remove(idx);

                //Write system message to that room
                r.chats.add(0, MessageInfo.createSystemMsg(client.username + " removed bot " + ru.userName));
            }
        }
        
        
        /// PROCESS UPDATING

        //Update client
        long lastConn = client.lastConn;
        client.lastConn = System.currentTimeMillis();
        if(client.isOnline == false) {
            client.isOnline = true;
            client.lastUpdate = System.currentTimeMillis();
        }

        //Updating category
        if(request.ownedCategIds != null) {
            //Get data in DB
            ResultSet s = _db.doQuery("SELECT * FROM `kategori`");

            //For each category in system..
            while(s.next()) {
                //Prepare vars
                int categId = s.getInt(1);
                int foundIdx= request.ownedCategIds.indexOf(categId);

                //Check whether the categ Id is already in client
                if(foundIdx < 0) {
                    //If not found, then add inform the new category to the client
                    if(response.updatedCategs == null) response.updatedCategs = new ArrayList<CategoryInfo>();
                    CategoryInfo categInfo  = new CategoryInfo();
                    categInfo.id            = categId;
                    categInfo.name          = s.getString(2);
                    categInfo.parentId      = s.getInt(3);
                    response.updatedCategs.add(categInfo);
                } else {
                    //TODO: If found, then check for update

                    //TODO: If it needs updating, inform it to client

                    //Delete the idx which the categd is found
                    request.ownedCategIds.remove(foundIdx);
                }
            }

            //If there's still categIds owned in client,
            if(request.ownedCategIds.size() > 0) response.deletedCategIds = request.ownedCategIds;
        }

        //Updating room names
        if(request.ownedRoomNames != null) {
            Set<Entry<String, RoomInfo>> set = _rooms.entrySet();
            for(Entry<String, RoomInfo> e : set) {
                RoomInfo room = e.getValue();

                //Check whether client has the room already
                int foundIdx = request.ownedRoomNames.indexOf(room.name);
                if(foundIdx < 0) {
                    //If none found, then add the new room to client
                    RoomInfo val    = new RoomInfo();
                    val.categoryId  = room.categoryId;
                    val.name        = room.name;
                    if(response.updatedRooms == null) response.updatedRooms = new ArrayList<RoomInfo>();
                    response.updatedRooms.add(val);
                } else {
                    //TODO: If found, then check for update

                    //TODO: If it needs updating, inform it to client

                    //Delete
                    request.ownedRoomNames.remove(foundIdx);
                }
            }

            //If there's still room names owned by client, the inform them to be deleted
            if(request.ownedRoomNames.size() > 0) {
                response.deletedRooms = request.ownedRoomNames;
            }
        }

        if(request.friendsNames != null) {
            //Get user who is followed by this user
            ResultSet s = _db.doQuery("SELECT user.* FROM user JOIN friend ON (idFollowing = user.id) WHERE idFollower = " + client.id);

            //For each friend in system..
            while(s.next()) {
                //Update friend
                UserInfo user= _users.get(s.getString(2));
                if(user == null) continue;
                if(user.isOnline && System.currentTimeMillis() - user.lastConn > OL_TIMEOUT) quitUser(user);
                
                //if not found, inform user
                int foundIdx = request.friendsNames.indexOf(user.username);
                if(foundIdx < 0) {
                    if(response.updatedFriends == null) response.updatedFriends = new ArrayList<UserInfo>();
                    UserInfo toClient = new UserInfo();
                    toClient.username = user.username;
                    toClient.isOnline = user.isOnline;
                    response.updatedFriends.add(toClient);
                } else {
                    //If found, check update
                    boolean isUpdated = client.lastConn < _users.get(user.username).lastUpdate;

                    //If update found, inform user
                    if(isUpdated) {
                        UserInfo toClient = new UserInfo();
                        toClient.username = user.username;
                        toClient.isOnline = user.isOnline;
                        if(response.updatedFriends == null) response.updatedFriends = new ArrayList<UserInfo>();
                        response.updatedFriends.add(toClient);
                    }

                    //Remove
                    request.friendsNames.remove(foundIdx);
                }
            }

            //If there's still friendsNames owned in client, inform the client that the friends is deleted
            if(request.friendsNames.size() > 0) response.deletedFriends = request.friendsNames;
        }

        if(request.openedRooms != null) {
            for(String roomName : request.openedRooms) {
                RoomInfo r = roomInfoForClient(client, roomName, lastConn, false);
                if(r != null) {
                    if(response.updatedOpenedRooms == null) response.updatedOpenedRooms = new ArrayList<RoomInfo>();
                    response.updatedOpenedRooms.add(r);
                }
            }
        }

        //Check for private messages
        if(client.pvChats.size() > 0) {
            response.updatedPvChats = new ArrayList<MessageInfo>(client.pvChats);
            client.pvChats.clear();
        }

        //Check if i'm kicked
        if(client.kickedFrom.size() > 0) {
            if(response.closedOpenedRooms == null) response.closedOpenedRooms = new ArrayList<String>();
            for(String name : client.kickedFrom) response.closedOpenedRooms.add(name);
            client.kickedFrom.clear();
        }

        return response;
    }

    protected UserInfo createUserInfo(String username) throws SQLException {
        //Query from db
        ResultSet s = _db.doQuery("SELECT * FROM user WHERE username = '" + username + "'");
        if(!s.next()) {
            //Handle error, username is not found
            return null;
        }

        //Assign vars
        UserInfo retval = new UserInfo();
        retval.id       = s.getInt(1);
        retval.username = s.getString(2);
        retval.name     = s.getString(5);
        retval.nRoom    = s.getInt(8);
        retval.pvChats  = new LinkedList<MessageInfo>();
        retval.kickedFrom = new LinkedList<String>();
        retval.lastConn = System.currentTimeMillis();
        retval.lastUpdate = retval.lastConn;
        retval.isOnline = true;
        retval.isBanned = s.getBoolean(6);

        //Get created rooms
        retval.room     = new ArrayList<String>();
        Set<Entry<String, RoomInfo>> set = _rooms.entrySet();
        for(Entry<String, RoomInfo> e : set) {
            RoomInfo room = e.getValue();
            if(room.owner.equals(username)) retval.room.add(room.name);
            
        }

        //Get all moderated categories
        retval.moderating = new ArrayList<String>();
        retval.moderatingId = new ArrayList<Integer>();
        s = _db.doQuery("SELECT id,nama from kategori where id IN (SELECT idKategori FROM moderasi WHERE idUser = '" + retval.id + "')");
        while(s.next()) {
            retval.moderating.add(s.getString(2));
            retval.moderatingId.add(s.getInt(1));
        }

        return retval;
    }

    protected void quitUser(UserInfo user) {
        user.isOnline   = false;
        user.lastUpdate = System.currentTimeMillis();

        //Make this user quit all room he joined
        Set<Entry<String, RoomInfo>> ee = _rooms.entrySet();
        for(Entry<String, RoomInfo> e : ee) {
            RoomInfo r = e.getValue();
            if(r.userJoins.containsKey(user.username)) {
                quitRoom(user, r);
            }
        }
    }

    protected void quitRoom(UserInfo client, RoomInfo r) {
        //Remove my self from room
        r.userJoins.remove(client.username);

        //Write system msg to that room
        r.chats.add(0, MessageInfo.createSystemMsg(client.username + " left the room"));

        //Check if I'm the owner, move the ownership if the room isn't persist
        if(!r.persist && r.owner.equals(client.username)) {
            //Get longest staying user
            long cur = System.currentTimeMillis();
            String longestUser = null;
            long longestTime = 0;
            Set<Entry<String, Long>> ee = r.userJoins.entrySet();
            for(Entry<String, Long> e : ee) {
                if(e.getValue() - cur > longestTime) {
                    longestTime = e.getValue() - cur;
                    longestUser = e.getKey();
                }
            }

            //If longest user found, switch ownership
            if(longestUser != null) {
                UserInfo newOwner = _users.get(longestUser);
                r.owner = longestUser;
                newOwner.room.add(r.name);
                client.room.remove(r.name);
            }
        }

        //If there's no one in the room and the room isn't persist, rem the room
        if(!r.persist && r.userJoins.isEmpty()) {
            _rooms.remove(r.name);
            client.room.remove(r.name);
        }
    }

    protected RoomInfo roomInfoForClient(UserInfo client, String roomName, long lastConn, boolean forceAvail) {
        //Get roomInfo
        RoomInfo room       = _rooms.get(roomName);
        if(room == null || !room.userJoins.containsKey(client.username)) return null;
        RoomInfo toClient   = new RoomInfo();
        long timeJoined     = room.userJoins.get(client.username);

        //Get only newest message
        toClient.chats  = new LinkedList<MessageInfo>();
        for(MessageInfo m : room.chats) {
            if(m.timeNum > lastConn) toClient.chats.add(m);
            else break;
        }

        //Only process if we need to update
        if(toClient.chats.size() > 0 || forceAvail) {
            //Other info
            toClient.persist    = room.persist;
            toClient.owner      = room.owner;
            toClient.name       = room.name;
            toClient.bots       = room.bots;
            toClient.categs     = room.categs;
            toClient.bans       = room.bans;
            

            //Get users
            toClient.users = new ArrayList<UserInfo>();
            Set<Entry<String, Long>> ee = room.userJoins.entrySet();
            for(Entry<String,Long> e : ee) {
                //Get single user
                UserInfo u = _users.get(e.getKey());

                //Check user online status
                if(System.currentTimeMillis() - u.lastConn > OL_TIMEOUT) quitUser(u);

                //Add it
                toClient.users.add(u);
            }

            //Return to user
            return toClient;
        }
        else {
            return null;
        }
    }
}
